package symbols;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * A very simple symbol table implementation for static scoping.
 * 
 * Each time a new scope is created this symbol table is instantiated
 * and it copies all the values from the parent environment. Then 
 * these values can be redefined without affecting the parent 
 * environment.
 * 
 * @author marc
 *
 */
public class Environment implements ISymbolTable {
	private Hashtable ht = new Hashtable();
	private ISymbolTable parent = null;
	private int depth = 0;	// 0: global scope ; 1 child of the global scope ; etc
	private String scopeName = "global scope";
	private boolean displayHashcodes = false;	// activate this manually to display hash codes for left values
		
	/**
	 * Builds a new environment which contains the old
	 * environment symbols (if any) and where these old
	 * values can be redefined. 
	 * Redefinition do not affect the parent environment.
	 * 
	 * @param st the old environment or null to init global scope
	 */
	public Environment(ISymbolTable st, String scopeName) {
		parent = st;
		this.depth = st.getDepth() + 1;
		this.scopeName = scopeName;
	}

	/**
	 * Instantiates a global scope environment (no parent)
	 * 
	 * @see Environment(ISymbolTable s)
	 *
	 */
	public Environment() {}
	
	/**
	 * Private constructor accessible from inside
	 * the class only for the purpose of cloning
	 * @param ht
	 * @param parent
	 */
	private Environment(Hashtable ht, ISymbolTable parent, int depth, String scopeName) {
		Enumeration keys = ht.keys();
		while(keys.hasMoreElements()) {
			String k = (String)keys.nextElement();
			//System.out.println("cloning " + k);
			Object val = ht.get(k);
			//this.ht.put(k, val);
			
			if(val instanceof Integer)
				this.ht.put(k, new Integer(((Integer)val).intValue()));
			else if(val instanceof String)
				this.ht.put(k, new String((String)val));
			else if(val instanceof HLSet) 
				this.ht.put(k, ((HLSet)val).clone());
			else if(val instanceof SLeftValue)
				this.ht.put(k, ((SLeftValue)val).clone());
			else //if(val instanceof SCallable)
				this.ht.put(k, val);	// no need to clone
				
		}
		this.parent = parent;
		this.depth = depth;
		this.scopeName = scopeName;
	}
	
	public Object clone() {
		ISymbolTable st = new Environment(ht, parent, depth, scopeName);		
		return st;
	}

	/**
	 * @deprecated use contains
	public ISymbol getInScope(String s) {
		ISymbol sym = (ISymbol)ht.get(s);
		return sym;
	}
	*/
	
	public ISymbol get(String s) {
		ISymbol sym = (ISymbol)ht.get(s);
		/*
		if(sym != null && sym instanceof SLeftValue && s.equals("result")) {
			SLeftValue lv = (SLeftValue)sym;
			System.out.println("::::--> found " + s + " - " + lv.name + " = " + lv.getValue() + " in ::" +
					scopeName + ":: (L" + depth + ") #" + this.hashCode());
		}
		*/
		if(sym == null)
			if(parent == null)
				return null;
			else {
				/*
				if(s.equals("result"))
					System.out.println(":::" + s + " NOT in ::" +
							scopeName + ":: (L" + depth + ") #" + this.hashCode());
							*/
				return (ISymbol)parent.get(s);
			}
		else
			return sym;
	}

	public void put(String s, ISymbol sym) {
		ht.put(s, sym);
	}

	public void addAllToScope(ISymbolTable t) {
		ht.putAll(t.getAll());
	}
		
	public Hashtable getAllInScope() {
		return ht;
	}
	
	public Hashtable getAll() {
		Hashtable res = new Hashtable();
		res.putAll(ht);
		// recursively add symbols from parent scopes
		if(parent != null)
			res.putAll(parent.getAll());
		return res;
	}
	
	/** 
	 * Set parent scope's symbol table
	 * @param st
	 */
	public void setParent(ISymbolTable st) {
		parent = st;
	}
	
	/**
	 * @return parent
	 */
	public ISymbolTable getParent() {
		return parent;
	}

	/**
	 * @return depth
	 */
	public int getDepth() {
		return depth;
	}
	
	/**
	 * @return the scope name of this symbol table (aka function name)
	 */
	public String getName() {
		return scopeName;
	}
	
	/**
	 * Applies to this scope only.
	 * Does not tell if parent scope(s) eventually
	 * contain the symbol with image s
	 */
	public boolean contains(String s) {
		return ht.get(s) != null;
	}

	public String toString() {
		boolean hasACallable = false;
		int pageNote = 0;
		StringBuffer sb = new StringBuffer();
		sb.append("\n ------ Symbol table ")
			.append("::").append(scopeName).append(":: ")
			.append("(L").append(depth).append(") ")
			.append(this.hashCode()).append(" -> ").append((parent!=null)?"" + parent.hashCode()+" ":"root -----").append("-------\n");
		Enumeration e = this.ht.keys();
		while(e.hasMoreElements()) {
			Object key = e.nextElement();
			ISymbol s = (ISymbol)ht.get(key);
			if(s instanceof SCallable)	hasACallable = true;
			sb.append("| ").append(key).append(" -> ").append(ht.get(key));
				// add hashcode to differentiate memory location - only for leftvalues
			if(! (s instanceof SCallable) && displayHashcodes)
				sb.append("\t\t(").append(ht.get(key).hashCode()).append(") ");
			if(s instanceof SCallable) sb.append(" (").append(++pageNote).append(")");
			sb.append("\n");
		}
		sb.append(" ---------------------------- end --------------------------------\n");
		
		if(!hasACallable) return sb.toString();
		
		// adding "callable" symbol tables as page note here
		e  = this.ht.keys();
		pageNote = 0;
		while(e.hasMoreElements()) {
			Object key = e.nextElement();
			ISymbol s = (ISymbol)ht.get(key);
			if(! (s instanceof SCallable)) continue;	// only interested in fn, sub, predicate
			sb.append("\n(").append(++pageNote).append(")")
				.append(((SCallable)s).getSymbolTable()).append("\n");			
		}
				
		return sb.toString();
	}
	
	public void print() {
		System.out.println(this);
	}

	public void print(OutputStream out) {
		new PrintWriter(out).println(this.toString());
	}
}
